﻿using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Sgr.Exceptions;
using StackExchange.Redis;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;

namespace Sgr.Redis
{
    /// <summary>
    /// Redis数据库连接上下文，负责创建与维护 <see cref="IDatabase"/> 实例，并在出现网络连接异常时按策略进行重连。
    /// 改进点:
    /// 1. 将时间阈值改为 static readonly 减少实例内存占用。
    /// 2. 抽取公共连接创建逻辑，减少同步/异步重复代码。
    /// 3. OnRedisError 增加日志，提升可观测性。
    /// 4. Dispose 时释放 SemaphoreSlim，并清理错误窗口时间戳。
    /// 5. 增加 TryGetDatabase、IsHealthy、ReconnectCount、LastConnectTimeUtc 便于外部诊断。
    /// 6. ValidateServerFeatures 提供实际版本信息。
    /// </summary>
    public class RedisDatabaseContext : IRedisDatabaseContext
    {
        private static readonly TimeSpan ReconnectMinInterval = TimeSpan.FromSeconds(60);    // 两次重连至少间隔 60s
        private static readonly TimeSpan ReconnectErrorThreshold = TimeSpan.FromSeconds(30); // 连续错误累计达到 30s 触发重连

        private readonly SemaphoreSlim _connectionLock = new(initialCount: 1, maxCount: 1);
        private readonly RedisOptions _options;
        private readonly RedisKey _instancePrefix;
        private readonly ILogger<RedisDatabaseContext>? _logger;

        private long _lastConnectTicks = DateTime.UtcNow.Ticks; // 最近一次成功建立连接时间
        private long _firstErrorTimeTicks;                      // 当前连续错误窗口首个错误时间
        private long _previousErrorTimeTicks;                   // 最近一次错误时间
        private volatile bool _disposed;
        private volatile IDatabase? _cache;

        private long _reconnectCount;                           // 重连次数（成功触发释放连接视为一次）
        private static readonly Version ServerVersionWithExtendedSetCommand = new(4, 0, 0);

        /// <summary>
        /// 构造函数
        /// </summary>
        public RedisDatabaseContext(IOptions<RedisOptions> options, ILogger<RedisDatabaseContext>? logger = null)
        {
            Check.NotNull(options, nameof(options));
            _options = options.Value;
            _logger = logger;

            if (string.IsNullOrEmpty(_options.InstanceName))
                _instancePrefix = (RedisKey)Encoding.UTF8.GetBytes(Environment.MachineName + ".");
            else
                _instancePrefix = (RedisKey)Encoding.UTF8.GetBytes(_options.InstanceName);
        }

        /// <summary>
        /// Redis Key 前缀
        /// </summary>
        public RedisKey InstancePrefix => _instancePrefix;

        /// <summary>
        /// 最近一次连接成功的 UTC 时间（若尚未连接返回 DateTime.MinValue）
        /// </summary>
        public DateTime LastConnectTimeUtc => ReadTimeTicks(ref _lastConnectTicks);

        /// <summary>
        /// 已触发的重连次数（释放旧连接计数一次）
        /// </summary>
        public long ReconnectCount => Volatile.Read(ref _reconnectCount);

        /// <summary>
        /// 当前上下文是否健康（未释放且存在连接并处于连接状态）
        /// </summary>
        public bool IsHealthy
        {
            get
            {
                if (_disposed) return false;
                var db = _cache;
                var mux = db?.Multiplexer;
                return mux?.IsConnected ?? false;
            }
        }

        /// <summary>
        /// 同步获取 IDatabase
        /// </summary>
        public IDatabase Connect()
        {
            CheckDisposed();
            var existing = _cache;
            if (existing != null)
                return existing;

            return AcquireDatabase();
        }

        /// <summary>
        /// 异步获取 IDatabase
        /// </summary>
        public ValueTask<IDatabase> ConnectAsync(CancellationToken token = default)
        {
            CheckDisposed();
            token.ThrowIfCancellationRequested();

            var existing = _cache;
            if (existing != null)
                return ValueTask.FromResult(existing);

            return AcquireDatabaseAsync(token);
        }

        /// <summary>
        /// 尝试获取当前已有的 IDatabase，不会触发连接创建。
        /// </summary>
        public bool TryGetDatabase(out IDatabase? database)
        {
            database = _cache;
            return database != null;
        }

        /// <summary>
        /// 处理 Redis 访问过程中出现的异常。
        /// 当启用了强制重连选项并且异常属于网络/连接类异常时，执行一个“连续错误窗口”判定逻辑：
        /// 1. 距离上一次建立连接成功已超过最小重连间隔(ReconnectMinInterval)。
        /// 2. 使用 first / previous 时间戳维护连续错误窗口；若间隔超过阈值则重置窗口。
        /// 3. 连续错误累计达到阈值(ReconnectErrorThreshold)触发一次重连。
        /// </summary>
        public void OnRedisError(Exception exception, IDatabase cache)
        {
            if (_disposed) return;

            if (_options.UseForceReconnectWhenConnectionException && (exception is RedisConnectionException or SocketException))
            {
                var utcNow = DateTime.UtcNow;
                var previousConnectTime = ReadTimeTicks(ref _lastConnectTicks);

                if (utcNow - previousConnectTime < ReconnectMinInterval)
                {
                    _logger?.LogDebug("Redis错误触发，但未达到最小重连间隔: {Interval}s", ReconnectMinInterval.TotalSeconds);
                    return;
                }

                var firstErrorTime = ReadTimeTicks(ref _firstErrorTimeTicks);
                var previousErrorTime = ReadTimeTicks(ref _previousErrorTimeTicks);

                if (firstErrorTime == DateTime.MinValue)
                {
                    WriteTimeTicks(ref _firstErrorTimeTicks, utcNow);
                    WriteTimeTicks(ref _previousErrorTimeTicks, utcNow);
                    _logger?.LogWarning(exception, "Redis第一次连接异常，开始连续错误窗口。");
                    return;
                }

                var elapsedSinceMostRecentError = utcNow - previousErrorTime;
                if (elapsedSinceMostRecentError > ReconnectErrorThreshold)
                {
                    WriteTimeTicks(ref _firstErrorTimeTicks, utcNow);
                    WriteTimeTicks(ref _previousErrorTimeTicks, utcNow);
                    _logger?.LogWarning(exception,
                        "Redis错误间隔 {Elapsed}s 超过阈值 {Threshold}s，重置连续错误窗口。",
                        elapsedSinceMostRecentError.TotalSeconds, ReconnectErrorThreshold.TotalSeconds);
                    return;
                }

                var elapsedSinceFirstError = utcNow - firstErrorTime;
                WriteTimeTicks(ref _previousErrorTimeTicks, utcNow);

                bool shouldReconnect = elapsedSinceFirstError >= ReconnectErrorThreshold;

                if (!shouldReconnect)
                {
                    _logger?.LogWarning(exception,
                        "Redis持续错误 {Elapsed}s 尚未达到重连阈值 {Threshold}s。",
                        elapsedSinceFirstError.TotalSeconds, ReconnectErrorThreshold.TotalSeconds);
                    return;
                }

                // 触发重连：清理窗口
                WriteTimeTicks(ref _firstErrorTimeTicks, DateTime.MinValue);
                WriteTimeTicks(ref _previousErrorTimeTicks, DateTime.MinValue);

                var tmp = Interlocked.CompareExchange(ref _cache, null, cache);
                if (ReferenceEquals(tmp, cache))
                {
                    WriteTimeTicks(ref _lastConnectTicks, utcNow);
                    ReleaseConnection(tmp);
                    Interlocked.Increment(ref _reconnectCount);

                    _logger?.LogWarning(exception,
                        "Redis触发强制重连。累计错误时长: {Elapsed}s, 重连次数: {ReconnectCount}",
                        elapsedSinceFirstError.TotalSeconds, ReconnectCount);
                }
                else
                {
                    _logger?.LogDebug("Redis重连被忽略：当前缓存引用已变化。");
                }
            }
        }

        public void Dispose()
        {
            if (_disposed) return;

            _disposed = true;
            var old = Interlocked.Exchange(ref _cache, null);
            ReleaseConnection(old);

            // 清理错误窗口时间戳
            WriteTimeTicks(ref _firstErrorTimeTicks, DateTime.MinValue);
            WriteTimeTicks(ref _previousErrorTimeTicks, DateTime.MinValue);

            _connectionLock.Dispose();
            GC.SuppressFinalize(this);
            _logger?.LogInformation("RedisDatabaseContext 已释放。");
        }

        #region 私有实现

        private IDatabase AcquireDatabase()
        {
            var cache = _cache;
            if (cache != null)
                return cache;

            _connectionLock.Wait();
            try
            {
                cache = _cache;
                if (cache != null)
                    return cache;

                var configuredOptions = _options.GetConfiguredOptions();
                var connection = ConnectionMultiplexer.Connect(configuredOptions);

                if (!connection.IsConnected)
                    throw new BusinessException("redis cannot connect!");

                PrepareConnection(connection);
                cache = _cache = connection.GetDatabase();

                var endPointsText = string.Join(", ", connection.GetEndPoints().Select(ep => ep.ToString()));
                _logger?.LogInformation("Redis连接建立成功(同步)。EndPoints: {EndPoints}", endPointsText);
                return cache;
            }
            finally
            {
                _connectionLock.Release();
            }
        }

        private async ValueTask<IDatabase> AcquireDatabaseAsync(CancellationToken token)
        {
            // 双检
            var cache = _cache;
            if (cache != null)
                return cache;

            await _connectionLock.WaitAsync(token).ConfigureAwait(false);
            try
            {
                cache = _cache;
                if (cache != null)
                    return cache;

                ConfigurationOptions configuredOptions = _options.GetConfiguredOptions();
                ConnectionMultiplexer connection = await ConnectionMultiplexer.ConnectAsync(configuredOptions).ConfigureAwait(false);

                if (!connection.IsConnected)
                    throw new InvalidOperationException("redis cannot connect!");

                PrepareConnection(connection);
                cache = _cache = connection.GetDatabase();

                var endPointsText = string.Join(", ", connection.GetEndPoints().Select(ep => ep.ToString()));
                _logger?.LogInformation("Redis连接建立成功(异步)。EndPoints: {EndPoints}", endPointsText);
                return cache;
            }
            finally
            {
                _connectionLock.Release();
            }
        }

        private void PrepareConnection(IConnectionMultiplexer connection)
        {
            ArgumentNullException.ThrowIfNull(connection);

            //// 订阅连接恢复事件
            //connection.ConnectionRestored += (sender, e) =>
            //{
            //    // 更新最后连接成功时间（使用 Volatile 写保证可见性）
            //    WriteTimeTicks(ref _lastConnectTicks, DateTime.UtcNow);

            //    _logger?.LogInformation(
            //        "Redis connection restored to endpoint: {EndPoint}. Connection type: {ConnectionType}",
            //        e.EndPoint,
            //        e.ConnectionType
            //    );
            //};

            //// 订阅连接失败事件
            //connection.ConnectionFailed += (sender, e) =>
            //{
            //    _logger?.LogWarning(
            //        e.Exception,
            //        "Redis connection failed to endpoint: {EndPoint}. Connection type: {ConnectionType}",
            //        e.EndPoint,
            //        e.ConnectionType
            //    );

            //    // 可选：在这里也可以触发指标上报（如 Prometheus.Counter）
            //    // Metrics.RedisConnectionFailures.WithLabels(e.EndPoint.ToString()).Inc();
            //};

            WriteTimeTicks(ref _lastConnectTicks, DateTime.UtcNow);
            ValidateServerFeatures(connection);
            TryRegisterProfiler(connection);
        }

        private void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
        }

        private void ReleaseConnection(IDatabase? cache)
        {
            var connection = cache?.Multiplexer;
            if (connection != null)
            {
                try
                {
                    connection.Dispose();
                }
                catch (Exception ex)
                {
                    _logger?.LogWarning(ex, "释放 Redis 连接时发生异常");
                }
            }
        }

        private static void ValidateServerFeatures(IConnectionMultiplexer connection)
        {
            _ = connection ?? throw new InvalidOperationException($"{nameof(connection)} cannot be null.");

            foreach (var endPoint in connection.GetEndPoints())
            {
                var server = connection.GetServer(endPoint);
                var version = server.Version;
                if (version < ServerVersionWithExtendedSetCommand)
                    throw new NotSupportedException($"Redis node {endPoint} must be 4.0+. Actual: {version}");
            }
        }

        private void TryRegisterProfiler(IConnectionMultiplexer connection)
        {
            _ = connection ?? throw new InvalidOperationException($"{nameof(connection)} cannot be null.");
            if (_options.ProfilingSession is not null)
            {
                connection.RegisterProfiler(_options.ProfilingSession);
            }
        }

        private static void WriteTimeTicks(ref long field, DateTime value)
        {
            var ticks = value == DateTime.MinValue ? 0L : value.Ticks;
            Volatile.Write(ref field, ticks);
        }

        private static DateTime ReadTimeTicks(ref long field)
        {
            var ticks = Volatile.Read(ref field);
            return ticks == 0 ? DateTime.MinValue : new DateTime(ticks, DateTimeKind.Utc);
        }

        #endregion 私有实现
    }
}